package com.ganwei.plugin;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.JarClassLoader;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import java.io.File;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


/**
 * @BelongsProject: plugin-demo-test
 * @BelongsPackage: com.plugin.demo
 * @Author: Owen
 * @CreateTime: 2022-12-27
 * @Description: 动态加载jar 加载类1
 * @Version: 1.0
 */
@Slf4j
public class PluginImportBeanDefinitionRegistrar1 implements ImportBeanDefinitionRegistrar, EnvironmentAware {


    //匹配数字的正则
    private static final Pattern pattern = Pattern.compile("[0-9]");

    /**
     * jar包存放路径
     */
    private String libPath;

    /**
     * 热加载类所在的包路径
     */
    private String classLoadPackages;

    private List<String> classLoadPackageList;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取要动态加载的jar列表
        List<String> fileNames = FileUtil.listFileNames(libPath);
        if(CollUtil.isEmpty(fileNames)){
            log.info("热加载文件件中，文件为空 ---------------> {}", libPath);
            return;
        }
        List<String> jarList = fileNames.stream().filter(jarName -> jarName.endsWith(".jar")).collect(Collectors.toList());
        if(CollUtil.isEmpty(jarList)){
            log.info("热加载文件件中，jar文件为空 ---------------> {}", libPath);
            return;
        }

        //获取要动态加载的jar列表

        //linux上没问题 window有问题
//        ApplicationHome home = new ApplicationHome();
//        File applicationJarFile = home.getSource();
//        String applicationJarFilePath = applicationJarFile.getAbsolutePath();

        String property = System.getProperty("user.dir");
        String property1 = System.getProperty("java.class.path");
        String applicationJarFilePath = property + File.separatorChar + property1;

        List<String> classJars = getDependence(applicationJarFilePath);
        log.info("主jar已经引用的jar：{}",classJars);

        //热加载类所在的包路径
        if(StrUtil.isBlank(classLoadPackages)){
            log.info("热加载类所在的包路径-未配置");
            return;
        }
        classLoadPackageList = Arrays.asList(classLoadPackages.split(","));

        //开始加载jar包
        try {
            //循环按顺序加载
            for (String jarName : jarList) {
                 if (validateJarAndVersion(classJars, jarName)) {
                    log.info("开始热加载jar包 ---------------> {}", jarName);
                    File jar = new File(libPath + File.separatorChar + jarName);
                    JarFile jarFile = new JarFile(jar);

                    JarClassLoader jarClassLoader = JarClassLoader.loadJar(jar);
                    for (Enumeration<JarEntry> ea = jarFile.entries(); ea.hasMoreElements(); ) {
                        JarEntry jarEntry = ea.nextElement();
                        String name = jarEntry.getName();
                        if (name.endsWith(".class") && isClassLoad(name)) {
                            String loadName = name.replace("/", ".").substring(0, name.length() - 6);
                            //加载class
                            Class<?> c = null;
                            try {
                                c = jarClassLoader.loadClass(loadName);
                            }catch (NoClassDefFoundError | Exception e){
                                log.error("热加载jar包中类异常");
                                e.printStackTrace();
                                continue;
                            }
                            //注册bean
                            insertBean(c, registry);
                        }
                    }
                    log.info("结束热加载jar包 ---------------> {}", jarName);
                } else {
                    log.info("依赖中已存在该jar包 ---------------> {}", jarName);
                }
            }
        } catch (Exception e) {
            log.error("热加载jar包异常");
            e.printStackTrace();
        }
    }

    private void insertBean(Class<?> c, BeanDefinitionRegistry registry) {
        if (isSpringBeanClass(c)) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(c);
            BeanDefinition beanDefinition = builder.getBeanDefinition();
            registry.registerBeanDefinition(c.getName(), beanDefinition);
        }
    }

    //获取依赖的jar包名称列表
    private List<String> getDependence(String jarPath) {
        List<String> result = new LinkedList<>();
        if (!jarPath.endsWith(".jar")) {
            return result;
        }
        try {
            log.info("jarPath:"+jarPath);
            JarFile jarFile = new JarFile(jarPath);
            for (Enumeration<JarEntry> ea = jarFile.entries(); ea.hasMoreElements(); ) {
                JarEntry jarEntry = ea.nextElement();
                if (jarEntry.getName().startsWith("BOOT-INF/lib/") && jarEntry.getName().endsWith(".jar")) {
                    result.add(jarEntry.getName().substring((jarEntry.getName().lastIndexOf("/") + 1)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    //判断是否存在同样的依赖jar包名，判断版本号高低
    private boolean validateJarAndVersion(List<String> dependenceList, String jarName) {
        boolean result = true;
        int firstNumIndex = getFirstNumIndex(jarName);
        String jarSimpleName = jarName.substring(0, firstNumIndex);
        for (String dependence : dependenceList) {
            if (dependence.startsWith(jarSimpleName)) {
                //判断版本号高低
                DefaultArtifactVersion version1 = new DefaultArtifactVersion(dependence);
                DefaultArtifactVersion version2 = new DefaultArtifactVersion(jarName);
                int res = version1.compareTo(version2);
                if(res >= 0){
                    //已经引入最新版本
                    log.info("dependence:{};jarName:{};res:{}",dependence,jarName,res);
                    result = false;
                }
                break;
            }
        }
        return result;
    }

    //获取名字中第一个出现数字的下标
    private int getFirstNumIndex(String jarName) {
        Matcher matcher = pattern.matcher(jarName);
        if (matcher.find()) {
            return matcher.start();
        } else {
            return -1;
        }
    }

    /**
     * 方法描述 判断class对象是否带有spring的注解
     *
     * @param cla jar中的每一个class
     * @return true 是spring bean   false 不是spring bean
     * @method isSpringBeanClass
     */
    public boolean isSpringBeanClass(Class<?> cla) {
        if (cla == null) {
            return false;
        }
        //是否是接口
        if (cla.isInterface()) {
            return false;
        }
        //是否是抽象类
        if (Modifier.isAbstract(cla.getModifiers())) {
            return false;
        }
        if (cla.getAnnotation(GanWeiPluginBean.class) != null) {
            return true;
        }
//        if (cla.getAnnotation(Component.class) != null) {
//            return true;
//        }
//        if (cla.getAnnotation(Repository.class) != null) {
//            return true;
//        }
//        if (cla.getAnnotation(Service.class) != null) {
//            return true;
//        }
        return false;
    }

    /**
     * 是否在热加载路径包下
     * @param fileName class文件全路径
     * @return
     */
    public boolean isClassLoad(String fileName) {
//        if (CollUtil.isEmpty(classLoadPackageList)) {
//            return false;
//        }
        for (String classLoadPackage : classLoadPackageList){
            if(fileName.startsWith(classLoadPackage)){
                return true;
            }
        }

        return false;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.libPath = environment.getProperty("libPath");
        classLoadPackages = environment.getProperty("classLoadPackages");
    }
}